Completed
Pull Request — master (#99)
by Ruben de
57s
created

APIClient.deleteWebhook   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
nc 1
dl 0
loc 5
rs 9.4285
nop 2
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
/**
22
 *
23
 * @param opt
24
 * @returns {*}
25
 */
26
function networkFromOptions(opt) {
27
    if (opt.bitcoinCash) {
28
        if (opt.regtest) {
29
            return bitcoin.networks.bitcoincashregtest;
30
        } else if (opt.testnet) {
31
            return bitcoin.networks.bitcoincashtestnet;
32
        } else {
33
            return bitcoin.networks.bitcoincash;
34
        }
35
    } else {
36
        if (opt.regtest) {
37
            return bitcoin.networks.regtest;
38
        } else if (opt.testnet) {
39
            return bitcoin.networks.testnet;
40
        } else {
41
            return bitcoin.networks.bitcoin;
42
        }
43
    }
44
}
45
46
var useWebWorker = require('./use-webworker')();
47
48
49
/**
50
 * helper to wrap a promise so that the callback get's called when it succeeds or fails
51
 *
52
 * @param promise   {q.Promise}
53
 * @param cb        function
54
 * @return q.Promise
55
 */
56
function callbackify(promise, cb) {
57
    // add a .then to trigger the cb for people using callbacks
58
    if (cb) {
59
        promise
60
            .then(function(res) {
61
                // use q.nextTick for asyncness
62
                q.nextTick(function() {
63
                    cb(null, res);
64
                });
65
            }, function(err) {
66
                // use q.nextTick for asyncness
67
                q.nextTick(function() {
68
                    cb(err, null);
69
                });
70
            });
71
    }
72
73
    // return the promise for people using promises
74
    return promise;
75
}
76
77
/**
78
 * Bindings to consume the BlockTrail API
79
 *
80
 * @param options       object{
81
 *                          apiKey: 'API_KEY',
82
 *                          apiSecret: 'API_SECRET',
83
 *                          host: 'defaults to api.blocktrail.com',
84
 *                          network: 'BTC|LTC',
85
 *                          testnet: true|false
86
 *                      }
87
 * @constructor
88
 */
89
var APIClient = function(options) {
90
    var self = this;
91
92
    // handle constructor call without 'new'
93
    if (!(this instanceof APIClient)) {
94
        return new APIClient(options);
95
    }
96
97
    var normalizedNetwork = APIClient.normalizeNetworkFromOptions(options);
98
    options.network = normalizedNetwork[0];
99
    options.testnet = normalizedNetwork[1];
100
    options.regtest = normalizedNetwork[2];
101
    // apiNetwork we allow to be customized for debugging purposes
102
    options.apiNetwork = options.apiNetwork || normalizedNetwork[3];
103
104
    self.bitcoinCash = options.network === "BCC";
105
    self.regtest = options.regtest;
106
    self.testnet = options.testnet;
107
    self.network = networkFromOptions(self);
108
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
109
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
110
111
    options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase());
112
113
    if (typeof options.btccom === "undefined") {
114
        options.btccom = true;
115
    }
116
117
    /**
118
     * @type RestClient
119
     */
120
    self.dataClient = APIClient.initRestClient(_.merge({}, options));
121
    /**
122
     * @type RestClient
123
     */
124
    self.blocktrailClient = APIClient.initRestClient(_.merge({}, options, {btccom: false}));
125
126
    if (options.btccom) {
127
        var rawTxOptions = APIClient.updateHostOptions(_.merge({}, options, {btccom: true}));
128
        var rawTxHost = rawTxOptions.host.replace(/\.api\.btc\.com$/, '.btc.com');
129
130
        self.converter = new BtccomConverter(self.network, true, rawTxHost);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
131
    } else {
132
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
133
    }
134
135
};
136
137
APIClient.normalizeNetworkFromOptions = function(options) {
138
    /* jshint -W071, -W074 */
139
    var network = 'BTC';
140
    var testnet = false;
141
    var regtest = false;
142
    var apiNetwork = "BTC";
0 ignored issues
show
Unused Code introduced by
The assignment to variable apiNetwork seems to be never used. Consider removing it.
Loading history...
143
144
    var prefix;
145
    var done = false;
146
147
    if (options.network) {
148
        var lower = options.network.toLowerCase();
149
150
        var m = lower.match(/^([rt])?(btc|bch|bcc)$/);
151
        if (!m) {
152
            throw new Error("Invalid network [" + options.network + "]");
153
        }
154
155
        if (m[2] === 'btc') {
156
            network = "BTC";
157
        } else {
158
            network = "BCC";
159
        }
160
161
        prefix = m[1];
162
        if (prefix) {
163
            // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after
164
            done = true;
165
            if (prefix === 'r') {
166
                testnet = true;
167
                regtest = true;
168
            } else if (prefix === 't') {
169
                testnet = true;
170
            }
171
        }
172
    }
173
174
    // if we're not already done then apply options.regtest and options.testnet
175
    if (!done) {
176
        if (options.regtest) {
177
            testnet = true;
178
            regtest = true;
179
            prefix = "r";
180
        } else if (options.testnet) {
181
            testnet = true;
182
            prefix = "t";
183
        }
184
    }
185
186
    apiNetwork = (prefix || "") + network;
187
188
    return [network, testnet, regtest, apiNetwork];
189
};
190
191
APIClient.updateHostOptions = function(options) {
192
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
193
    if (process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
194
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
195
    }
196
197
    // trim off leading https?://
198
    if (options.host && options.host.indexOf("https://") === 0) {
199
        options.https = true;
200
        options.host = options.host.substr(8);
201
    } else if (options.host && options.host.indexOf("http://") === 0) {
202
        options.https = false;
203
        options.host = options.host.substr(7);
204
    }
205
206
    if (typeof options.https === "undefined") {
207
        options.https = true;
208
    }
209
210
    if (!options.port) {
211
        options.port = options.https ? 443 : 80;
212
    }
213
214
    if (options.btccom) {
215
        if (!options.host) {
216
            options.host = (options.testnet && 't' || '') + 'chain.api.btc.com';
217
        }
218
219
        if (!options.endpoint) {
220
            options.endpoint = "/" + (options.apiVersion || "v3");
221
        }
222
    } else {
223
        if (!options.host) {
224
            options.host = 'api.blocktrail.com';
225
        }
226
227
        if (!options.endpoint) {
228
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
229
        }
230
    }
231
232
    return options;
233
};
234
235
APIClient.initRestClient = function(options) {
236
    options = APIClient.updateHostOptions(options);
237
    return new RestClient(options);
238
};
239
240
var determineDataStorageV2_3 = function(options) {
241
    return q.when(options)
242
        .then(function(options) {
243
            // legacy
244
            if (options.storePrimaryMnemonic) {
245
                options.storeDataOnServer = options.storePrimaryMnemonic;
246
            }
247
248
            // storeDataOnServer=false when primarySeed is provided
249
            if (typeof options.storeDataOnServer === "undefined") {
250
                options.storeDataOnServer = !options.primarySeed;
251
            }
252
253
            return options;
254
        });
255
};
256
257
var produceEncryptedDataV2 = function(options, notify) {
258
    return q.when(options)
259
        .then(function(options) {
260
            if (options.storeDataOnServer) {
261
                if (!options.secret) {
262
                    if (!options.passphrase) {
263
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
264
                    }
265
266
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
267
268
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
269
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
270
                }
271
272
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
273
274
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
275
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
276
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
277
278
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
279
280
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
281
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
282
            }
283
284
            return options;
285
        });
286
};
287
288
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
289
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
290
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
291
        var saltBuf = Encryption.generateSalt();
292
        var iv = Encryption.generateIV();
293
294
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
295
            return require('./webworker');
296
        }, onLoadWorkerLoadAsmCrypto, {
297
            method: 'Encryption.encryptWithSaltAndIV',
298
            pt: pt,
299
            pw: pw,
300
            saltBuf: saltBuf,
301
            iv: iv,
302
            iterations: iter
303
        })
304
            .then(function(data) {
305
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
306
            });
307
    } else {
308
        try {
309
            return q.when(Encryption.encrypt(pt, pw, iter));
310
        } catch (e) {
311
            return q.reject(e);
312
        }
313
    }
314
};
315
316
APIClient.prototype.promisedDecrypt = function(ct, pw) {
317
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
318
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
319
            return require('./webworker');
320
        }, onLoadWorkerLoadAsmCrypto, {
321
            method: 'Encryption.decrypt',
322
            ct: ct,
323
            pw: pw
324
        })
325
            .then(function(data) {
326
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
327
            });
328
    } else {
329
        try {
330
            return q.when(Encryption.decrypt(ct, pw));
331
        } catch (e) {
332
            return q.reject(e);
333
        }
334
    }
335
};
336
337
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
338
    var self = this;
339
340
    return q.when(options)
341
        .then(function(options) {
342
            if (options.storeDataOnServer) {
343
                return q.when()
344
                    .then(function() {
345
                        if (!options.secret) {
346
                            if (!options.passphrase) {
347
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
348
                            }
349
350
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
351
352
                            // -> now a buffer
353
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
354
355
                            // -> now a buffer
356
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
357
                                .then(function(encryptedSecret) {
358
                                    options.encryptedSecret = encryptedSecret;
359
                                });
360
                        } else {
361
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
362
                                throw new Error('Secret must be a buffer');
363
                            }
364
                        }
365
                    })
366
                    .then(function() {
367
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
368
369
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
370
                            .then(function(encryptedPrimarySeed) {
371
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
372
                            });
373
                    })
374
                    .then(function() {
375
                        // skip generating recovery secret when explicitly set to false
376
                        if (options.recoverySecret === false) {
377
                            return;
378
                        }
379
380
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
381
                        if (!options.recoverySecret) {
382
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
383
                        }
384
385
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
386
                            .then(function(recoveryEncryptedSecret) {
387
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
388
                            });
389
                    })
390
                    .then(function() {
391
                        return options;
392
                    });
393
            } else {
394
                return options;
395
            }
396
        });
397
};
398
399
var doRemainingWalletDataV2_3 = function(options, network, notify) {
400
    return q.when(options)
401
        .then(function(options) {
402
            if (!options.backupPublicKey) {
403
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
404
            }
405
406
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
407
408
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
409
410
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
411
412
            if (!options.backupPublicKey) {
413
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
414
                options.backupPublicKey = options.backupPrivateKey.neutered();
415
            }
416
417
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
418
419
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
420
421
            return options;
422
        });
423
};
424
425
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
426
    var self = this;
427
428
    var deferred = q.defer();
429
    deferred.promise.spreadNodeify(cb);
430
431
    deferred.resolve(q.fcall(function() {
432
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
433
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
434
        });
435
    }));
436
437
    return deferred.promise;
438
};
439
440
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
441
    var self = this;
442
443
    if (useWebWorker) {
444
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
445
            return require('./webworker');
446
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
447
            .then(function(data) {
448
                return data.seed;
449
            });
450
    } else {
451
        try {
452
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
453
        } catch (e) {
454
            return q.reject(e);
455
        }
456
    }
457
};
458
459
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
460
    var self = this;
461
462
    var deferred = q.defer();
463
    deferred.promise.nodeify(cb);
464
465
    try {
466
        // avoid conflicting options
467
        if (options.passphrase && options.password) {
468
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
469
        }
470
        // normalize passphrase/password
471
        options.passphrase = options.passphrase || options.password;
472
        delete options.password;
473
474
        // avoid conflicting options
475
        if (options.primaryMnemonic && options.primarySeed) {
476
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
477
        }
478
479
        // avoid deprecated options
480
        if (options.primaryPrivateKey) {
481
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
482
        }
483
484
        // make sure we have at least one thing to use
485
        if (!options.primaryMnemonic && !options.primarySeed) {
486
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
487
        }
488
489
        if (options.primarySeed) {
490
            self.primarySeed = options.primarySeed;
491
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
492
            deferred.resolve(options);
493
        } else {
494
            if (!options.passphrase) {
495
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
496
            }
497
498
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
499
                .then(function(seedHex) {
500
                    try {
501
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
502
                        options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network);
503
                        deferred.resolve(options);
504
                    } catch (e) {
505
                        deferred.reject(e);
506
                    }
507
                }, function(e) {
508
                    deferred.reject(e);
509
                });
510
        }
511
    } catch (e) {
512
        deferred.reject(e);
513
    }
514
515
    return deferred.promise;
516
};
517
518
APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) {
519
    var self = this;
520
521
    var deferred = q.defer();
522
    deferred.promise.nodeify(cb);
523
524
    try {
525
        // avoid conflicting options
526
        if (options.backupMnemonic && options.backupPublicKey) {
527
            throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey");
528
        }
529
530
        // make sure we have at least one thing to use
531
        if (!options.backupMnemonic && !options.backupPublicKey) {
532
            throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey");
533
        }
534
535
        if (options.backupPublicKey) {
536
            if (options.backupPublicKey instanceof bitcoin.HDNode) {
537
                deferred.resolve(options);
538
            } else {
539
                options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network);
540
                deferred.resolve(options);
541
            }
542
        } else {
543
            self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) {
544
                options.backupPublicKey = backupPrivateKey.neutered();
545
                deferred.resolve(options);
546
            }, function(e) {
547
                deferred.reject(e);
548
            });
549
        }
550
    } catch (e) {
551
        deferred.reject(e);
552
    }
553
554
    return deferred.promise;
555
};
556
557
APIClient.prototype.debugAuth = function(cb) {
558
    var self = this;
559
560
    return self.dataClient.get("/debug/http-signature", null, true, cb);
561
};
562
563
/**
564
 * get a single address
565
 *
566
 * @param address      string       address hash
567
 * @param [cb]          function    callback function to call when request is complete
568
 * @return q.Promise
569
 */
570
APIClient.prototype.address = function(address, cb) {
571
    var self = this;
572
573
    return callbackify(self.dataClient.get(self.converter.getUrlForAddress(address), null)
574
        .then(function(data) {
575
            return self.converter.handleErros(self, data);
576
        })
577
        .then(function(data) {
578
            if (data === null) {
579
                return data;
580
            } else {
581
                return self.converter.convertAddress(data);
582
            }
583
        }), cb);
584
};
585
586
APIClient.prototype.addresses = function(addresses, cb) {
587
    var self = this;
588
589
    return callbackify(self.dataClient.post("/address", null, {"addresses": addresses}), cb);
590
};
591
592
593
/**
594
 * get all transactions for an address (paginated)
595
 *
596
 * @param address       string      address hash
597
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
598
 * @param [cb]          function    callback function to call when request is complete
599
 * @return q.Promise
600
 */
601
APIClient.prototype.addressTransactions = function(address, params, cb) {
602
603
    var self = this;
604
605
    if (typeof params === "function") {
606
        cb = params;
607
        params = null;
608
    }
609
610
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
611
        .then(function(data) {
612
            return self.converter.handleErros(self, data);
613
        })
614
        .then(function(data) {
615
            return data.data === null ? data : self.converter.convertAddressTxs(data);
616
        }), cb);
617
};
618
619
/**
620
 * get all transactions for a batch of addresses (paginated)
621
 *
622
 * @param addresses     array       address hashes
623
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
624
 * @param [cb]          function    callback function to call when request is complete
625
 * @return q.Promise
626
 */
627
APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) {
628
    var self = this;
629
630
    if (typeof params === "function") {
631
        cb = params;
632
        params = null;
633
    }
634
635
    return self.dataClient.post("/address/has-transactions", self.converter.paginationParams(params), {"addresses": addresses}, cb);
636
};
637
638
/**
639
 * get all unconfirmed transactions for an address (paginated)
640
 *
641
 * @param address       string      address hash
642
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
643
 * @param [cb]          function    callback function to call when request is complete
644
 * @return q.Promise
645
 */
646
APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) {
647
    var self = this;
648
649
    if (typeof params === "function") {
650
        cb = params;
651
        params = null;
652
    }
653
654
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
655
        .then(function(data) {
656
            return self.converter.handleErros(self, data);
657
        })
658
        .then(function(data) {
659
            if (data.data === null) {
660
                return data;
661
            }
662
663
            var res = self.converter.convertAddressTxs(data);
664
            res.data = res.data.filter(function(tx) {
665
                return !tx.confirmations;
666
            });
667
668
            return res;
669
        }), cb);
670
};
671
672
/**
673
 * get all unspent outputs for an address (paginated)
674
 *
675
 * @param address       string      address hash
676
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
677
 * @param [cb]          function    callback function to call when request is complete
678
 * @return q.Promise
679
 */
680
APIClient.prototype.addressUnspentOutputs = function(address, params, cb) {
681
    var self = this;
682
683
    if (typeof params === "function") {
684
        cb = params;
685
        params = null;
686
    }
687
688
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressUnspent(address), self.converter.paginationParams(params))
689
        .then(function(data) {
690
            return self.converter.handleErros(self, data);
691
        })
692
        .then(function(data) {
693
            return data.data === null ? data : self.converter.convertAddressUnspentOutputs(data, address);
694
        }), cb);
695
};
696
697
/**
698
 * get all unspent outputs for a batch of addresses (paginated)
699
 *
700
 * @param addresses     array       address hashes
701
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
702
 * @param [cb]          function    callback function to call when request is complete
703
 * @return q.Promise
704
 */
705
APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) {
706
    var self = this;
707
708
    if (self.converter instanceof BtccomConverter) {
709
        throw new Error("Not implemented");
710
    }
711
712
    if (typeof params === "function") {
713
        cb = params;
714
        params = null;
715
    }
716
717
    return callbackify(self.dataClient.post("/address/unspent-outputs", params, {"addresses": addresses}), cb);
718
};
719
720
/**
721
 * verify ownership of an address
722
 *
723
 * @param address       string      address hash
724
 * @param signature     string      a signed message (the address hash) using the private key of the address
725
 * @param [cb]          function    callback function to call when request is complete
726
 * @return q.Promise
727
 */
728
APIClient.prototype.verifyAddress = function(address, signature, cb) {
729
    var self = this;
730
731
    return self.verifyMessage(address, address, signature, cb);
732
};
733
734
/**
735
 *
736
 * get all blocks (paginated)
737
 * ASK
738
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
739
 * @param [cb]          function    callback function to call when request is complete
740
 * @return q.Promise
741
 */
742
APIClient.prototype.allBlocks = function(params, cb) {
743
    var self = this;
744
745
    if (self.converter instanceof BtccomConverter) {
746
        throw new Error("Not implemented");
747
    }
748
749
    if (typeof params === "function") {
750
        cb = params;
751
        params = null;
752
    }
753
754
    return callbackify(self.dataClient.get(self.converter.getUrlForAllBlocks(), self.converter.paginationParams(params)), cb);
755
};
756
757
/**
758
 * get a block
759
 *
760
 * @param block         string|int  a block hash or a block height
761
 * @param [cb]          function    callback function to call when request is complete
762
 * @return q.Promise
763
 */
764
APIClient.prototype.block = function(block, cb) {
765
    var self = this;
766
767
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock(block), null)
768
        .then(function(data) {
769
            return self.converter.handleErros(self, data);
770
        })
771
        .then(function(data) {
772
            return data.data === null ? data : self.converter.convertBlock(data);
773
        }), cb);
774
};
775
776
/**
777
 * get the latest block
778
 *
779
 * @param [cb]          function    callback function to call when request is complete
780
 * @return q.Promise
781
 */
782
APIClient.prototype.blockLatest = function(cb) {
783
    var self = this;
784
785
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock("latest"), null)
786
        .then(function(data) {
787
            return self.converter.handleErros(self, data);
788
        })
789
        .then(function(data) {
790
            return data.data === null ? data : self.converter.convertBlock(data);
791
        }), cb);
792
};
793
794
/**
795
 * get all transactions for a block (paginated)
796
 *
797
 * @param block         string|int  a block hash or a block height
798
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
799
 * @param [cb]          function    callback function to call when request is complete
800
 * @return q.Promise
801
 */
802
APIClient.prototype.blockTransactions = function(block, params, cb) {
803
    var self = this;
804
805
    if (typeof params === "function") {
806
        cb = params;
807
        params = null;
808
    }
809
810
    return callbackify(self.dataClient.get(self.converter.getUrlForBlockTransaction(block), self.converter.paginationParams(params))
811
        .then(function(data) {
812
            return self.converter.handleErros(self, data);
813
        })
814
        .then(function(data) {
815
            return data.data ===  null ? data : self.converter.convertBlockTxs(data);
816
        }), cb);
817
};
818
819
/**
820
 * get a single transaction
821
 *
822
 * @param tx            string      transaction hash
823
 * @param [cb]          function    callback function to call when request is complete
824
 * @return q.Promise
825
 */
826
APIClient.prototype.transaction = function(tx, cb) {
827
    var self = this;
828
829
    return callbackify(self.dataClient.get(self.converter.getUrlForTransaction(tx), null)
830
        .then(function(data) {
831
            return self.converter.handleErros(self, data);
832
        })
833
        .then(function(data) {
834
            if (data.data === null) {
835
                return data;
836
            } else {
837
                // for BTC.com API we need to fetch the raw hex from the BTC.com explorer endpoint
838
                if (self.converter instanceof BtccomConverter) {
839
                    var txPath = data.data.hash + ".rawhex";
840
                    return self.converter.specialRawTxClientOnly.get(txPath, null, false)
841
                        .then(function(rawTx) {
842
                            return [data, rawTx];
843
                        })
844
                        .then(function(dataAndTx) {
845
                            if (dataAndTx !== null) {
846
                                var data = dataAndTx[0];
847
                                var rawTx = dataAndTx[1];
848
                                return self.converter.convertTx(data, rawTx);
849
                            } else {
850
                                return dataAndTx;
851
                            }
852
                        });
853
                } else {
854
                    return self.converter.convertTx(data);
855
                }
856
            }
857
        }), cb);
858
};
859
860
/**
861
 * get a batch of transactions
862
 *
863
 * @param txs           string[]    list of transaction hashes (txId)
864
 * @param [cb]          function    callback function to call when request is complete
865
 * @return q.Promise
866
 */
867
APIClient.prototype.transactions = function(txs, cb) {
868
    var self = this;
869
870
    if (self.converter instanceof BtccomConverter) {
871
        return callbackify(self.dataClient.get(self.converter.getUrlForTransactions(txs), null)
872
            .then(function(data) {
873
                return self.converter.handleErros(self, data);
874
            })
875
            .then(function(data) {
876
                if (data.data === null) {
877
                    return data;
878
                } else {
879
                    return self.converter.convertTxs(data);
880
                }
881
            }), cb);
882
    } else {
883
        return callbackify(self.dataClient.post("/transactions", null, txs, null, false), cb);
884
    }
885
};
886
887
/**
888
 * get a paginated list of all webhooks associated with the api user
889
 *
890
 * @param [params]      object      pagination: {page: 1, limit: 20}
891
 * @param [cb]          function    callback function to call when request is complete
892
 * @return q.Promise
893
 */
894
APIClient.prototype.allWebhooks = function(params, cb) {
895
    var self = this;
896
897
    if (typeof params === "function") {
898
        cb = params;
899
        params = null;
900
    }
901
902
    return self.blocktrailClient.get("/webhooks", params, cb);
903
};
904
905
/**
906
 * create a new webhook
907
 *
908
 * @param url           string      the url to receive the webhook events
909
 * @param [identifier]  string      a unique identifier associated with the webhook
910
 * @param [cb]          function    callback function to call when request is complete
911
 * @return q.Promise
912
 */
913
APIClient.prototype.setupWebhook = function(url, identifier, cb) {
914
    var self = this;
915
916
    if (typeof identifier === "function") {
917
        //mimic function overloading
918
        cb = identifier;
919
        identifier = null;
920
    }
921
922
    return self.blocktrailClient.post("/webhook", null, {url: url, identifier: identifier}, cb);
923
};
924
925
/**
926
 * Converts a cash address to the legacy (base58) format
927
 * @param {string} input
928
 * @returns {string}
929
 */
930
APIClient.prototype.getLegacyBitcoinCashAddress = function(input) {
931
    if (this.network === bitcoin.networks.bitcoincash ||
932
        this.network === bitcoin.networks.bitcoincashtestnet ||
933
        this.network === bitcoin.networks.bitcoincashregtest) {
934
        var address;
935
        try {
936
            bitcoin.address.fromBase58Check(input, this.network);
937
            return input;
938
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
939
940
        address = bitcoin.address.fromCashAddress(input, this.network);
941
        var prefix;
942
        if (address.version === bitcoin.script.types.P2PKH) {
943
            prefix = this.network.pubKeyHash;
944
        } else if (address.version === bitcoin.script.types.P2SH) {
945
            prefix = this.network.scriptHash;
946
        } else {
947
            throw new Error("Unsupported address type");
948
        }
949
950
        return bitcoin.address.toBase58Check(address.hash, prefix);
951
    }
952
953
    throw new Error("Cash addresses only work on bitcoin cash");
954
};
955
956
/**
957
 * Converts a legacy bitcoin to the new cashaddr format
958
 * @param {string} input
959
 * @returns {string}
960
 */
961
APIClient.prototype.getCashAddressFromLegacyAddress = function(input) {
962
    if (this.network === bitcoin.networks.bitcoincash ||
963
        this.network === bitcoin.networks.bitcoincashtestnet ||
964
        this.network === bitcoin.networks.bitcoincashregtest
965
    ) {
966
        var address;
967
        try {
968
            bitcoin.address.fromCashAddress(input, this.network);
969
            return input;
970
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
971
972
        address = bitcoin.address.fromBase58Check(input, this.network);
973
        var scriptType;
974
        if (address.version === this.network.pubKeyHash) {
975
            scriptType = bitcoin.script.types.P2PKH;
976
        } else if (address.version === this.network.scriptHash) {
977
            scriptType = bitcoin.script.types.P2SH;
978
        } else {
979
            throw new Error("Unsupported address type");
980
        }
981
982
        return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix);
983
    }
984
985
    throw new Error("Cash addresses only work on bitcoin cash");
986
};
987
988
/**
989
 * get an existing webhook by it's identifier
990
 *
991
 * @param identifier    string      the unique identifier of the webhook to get
992
 * @param [cb]          function    callback function to call when request is complete
993
 * @return q.Promise
994
 */
995
APIClient.prototype.getWebhook = function(identifier, cb) {
996
    var self = this;
997
998
    return self.blocktrailClient.get("/webhook/" + identifier, null, cb);
999
};
1000
1001
/**
1002
 * update an existing webhook
1003
 *
1004
 * @param identifier    string      the unique identifier of the webhook
1005
 * @param webhookData   object      the data to update: {identifier: newIdentifier, url:newUrl}
1006
 * @param [cb]          function    callback function to call when request is complete
1007
 * @return q.Promise
1008
 */
1009
APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) {
1010
    var self = this;
1011
1012
    return self.blocktrailClient.put("/webhook/" + identifier, null, webhookData, cb);
1013
};
1014
1015
/**
1016
 * deletes an existing webhook and any event subscriptions associated with it
1017
 *
1018
 * @param identifier    string      the unique identifier of the webhook
1019
 * @param [cb]          function    callback function to call when request is complete
1020
 * @return q.Promise
1021
 */
1022
APIClient.prototype.deleteWebhook = function(identifier, cb) {
1023
    var self = this;
1024
1025
    return self.blocktrailClient.delete("/webhook/" + identifier, null, null, cb);
1026
};
1027
1028
/**
1029
 * get a paginated list of all the events a webhook is subscribed to
1030
 *
1031
 * @param identifier    string      the unique identifier of the webhook
1032
 * @param [params]      object      pagination: {page: 1, limit: 20}
1033
 * @param [cb]          function    callback function to call when request is complete
1034
 * @return q.Promise
1035
 */
1036
APIClient.prototype.getWebhookEvents = function(identifier, params, cb) {
1037
    var self = this;
1038
1039
    if (typeof params === "function") {
1040
        cb = params;
1041
        params = null;
1042
    }
1043
1044
    return self.blocktrailClient.get("/webhook/" + identifier + "/events", params, cb);
1045
};
1046
1047
/**
1048
 * subscribes a webhook to transaction events for a particular transaction
1049
 *
1050
 * @param identifier    string      the unique identifier of the webhook
1051
 * @param transaction   string      the transaction hash
1052
 * @param confirmations integer     the amount of confirmations to send
1053
 * @param [cb]          function    callback function to call when request is complete
1054
 * @return q.Promise
1055
 */
1056
APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) {
1057
    var self = this;
1058
    var postData = {
1059
        'event_type': 'transaction',
1060
        'transaction': transaction,
1061
        'confirmations': confirmations
1062
    };
1063
1064
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1065
};
1066
1067
/**
1068
 * subscribes a webhook to transaction events on a particular address
1069
 *
1070
 * @param identifier    string      the unique identifier of the webhook
1071
 * @param address       string      the address hash
1072
 * @param confirmations integer     the amount of confirmations to send
1073
 * @param [cb]          function    callback function to call when request is complete
1074
 * @return q.Promise
1075
 */
1076
APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) {
1077
    var self = this;
1078
    var postData = {
1079
        'event_type': 'address-transactions',
1080
        'address': address,
1081
        'confirmations': confirmations
1082
    };
1083
1084
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1085
};
1086
1087
/**
1088
 * batch subscribes a webhook to multiple transaction events
1089
 *
1090
 * @param  identifier   string      the unique identifier of the webhook
1091
 * @param  batchData    array       An array of objects containing batch event data:
1092
 *                                  {address : 'address', confirmations : 'confirmations']
1093
 *                                  where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send
1094
 * @param [cb]          function    callback function to call when request is complete
1095
 * @return q.Promise
1096
 */
1097
APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) {
1098
    var self = this;
1099
    batchData.forEach(function(record) {
1100
        record.event_type = 'address-transactions';
1101
    });
1102
1103
    return self.blocktrailClient.post("/webhook/" + identifier + "/events/batch", null, batchData, cb);
1104
};
1105
1106
/**
1107
 * subscribes a webhook to a new block event
1108
 *
1109
 * @param identifier    string      the unique identifier of the webhook
1110
 * @param [cb]          function    callback function to call when request is complete
1111
 * @return q.Promise
1112
 */
1113
APIClient.prototype.subscribeNewBlocks = function(identifier, cb) {
1114
    var self = this;
1115
    var postData = {
1116
        'event_type': 'block'
1117
    };
1118
1119
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1120
};
1121
1122
/**
1123
 * removes an address transaction event subscription from a webhook
1124
 *
1125
 * @param identifier    string      the unique identifier of the webhook
1126
 * @param address       string      the address hash
1127
 * @param [cb]          function    callback function to call when request is complete
1128
 * @return q.Promise
1129
 */
1130
APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) {
1131
    var self = this;
1132
1133
    return self.blocktrailClient.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb);
1134
};
1135
1136
/**
1137
 * removes an transaction event subscription from a webhook
1138
 *
1139
 * @param identifier    string      the unique identifier of the webhook
1140
 * @param transaction   string      the transaction hash
1141
 * @param [cb]          function    callback function to call when request is complete
1142
 * @return q.Promise
1143
 */
1144
APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) {
1145
    var self = this;
1146
1147
    return self.blocktrailClient.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb);
1148
};
1149
1150
/**
1151
 * removes a block event subscription from a webhook
1152
 *
1153
 * @param identifier    string      the unique identifier of the webhook
1154
 * @param [cb]          function    callback function to call when request is complete
1155
 * @return q.Promise
1156
 */
1157
APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) {
1158
    var self = this;
1159
1160
    return self.blocktrailClient.delete("/webhook/" + identifier + "/block", null, null, cb);
1161
};
1162
1163
/**
1164
 * initialize an existing wallet
1165
 *
1166
 * Either takes two argument:
1167
 * @param options       object      {}
1168
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1169
 *
1170
 * Or takes three arguments (old, deprecated syntax):
1171
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1172
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1173
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1168. The second definition is ignored.
Loading history...
1174
 *
1175
 * @returns {q.Promise}
1176
 */
1177
APIClient.prototype.initWallet = function(options, cb) {
1178
    var self = this;
1179
1180
    if (typeof options !== "object") {
1181
        // get the old-style arguments
1182
        options = {
1183
            identifier: arguments[0],
1184
            passphrase: arguments[1]
1185
        };
1186
1187
        cb = arguments[2];
1188
    }
1189
1190
    if (options.check_backup_key) {
1191
        if (typeof options.check_backup_key !== "string") {
1192
            throw new Error("Invalid input, must provide the backup key as a string (the xpub)");
1193
        }
1194
    }
1195
1196
    var deferred = q.defer();
1197
    deferred.promise.spreadNodeify(cb);
1198
1199
    var identifier = options.identifier;
1200
1201
    if (!identifier) {
1202
        deferred.reject(new blocktrail.WalletInitError("Identifier is required"));
1203
        return deferred.promise;
1204
    }
1205
1206
    deferred.resolve(self.blocktrailClient.get("/wallet/" + identifier, null, true).then(function(result) {
1207
        var keyIndex = options.keyIndex || result.key_index;
1208
1209
        options.walletVersion = result.wallet_version;
1210
1211
        if (options.check_backup_key) {
1212
            if (options.check_backup_key !== result.backup_public_key[0]) {
1213
                throw new Error("Backup key returned from server didn't match our own copy");
1214
            }
1215
        }
1216
        var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network);
1217
        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1218
            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1219
        });
1220
        var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) {
1221
            return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network);
1222
        });
1223
1224
        // initialize wallet
1225
        var wallet = new Wallet(
1226
            self,
1227
            identifier,
1228
            options.walletVersion,
1229
            result.primary_mnemonic,
1230
            result.encrypted_primary_seed,
1231
            result.encrypted_secret,
1232
            primaryPublicKeys,
1233
            backupPublicKey,
1234
            blocktrailPublicKeys,
1235
            keyIndex,
1236
            result.segwit || 0,
1237
            self.testnet,
1238
            self.regtest,
1239
            result.checksum,
1240
            result.upgrade_key_index,
1241
            options.useCashAddress,
1242
            options.bypassNewAddressCheck
1243
        );
1244
1245
        wallet.recoverySecret = result.recovery_secret;
1246
1247
        if (!options.readOnly) {
1248
            return wallet.unlock(options).then(function() {
1249
                return wallet;
1250
            });
1251
        } else {
1252
            return wallet;
1253
        }
1254
    }));
1255
1256
    return deferred.promise;
1257
};
1258
1259
APIClient.CREATE_WALLET_PROGRESS_START = 0;
1260
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4;
1261
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5;
1262
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6;
1263
APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10;
1264
APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20;
1265
APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30;
1266
APIClient.CREATE_WALLET_PROGRESS_INIT = 40;
1267
APIClient.CREATE_WALLET_PROGRESS_DONE = 100;
1268
1269
/**
1270
 * create a new wallet
1271
 *   - will generate a new primary seed and backup seed
1272
 *
1273
 * Either takes two argument:
1274
 * @param options       object      {}
1275
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1276
 *
1277
 * For v1 wallets (explicitly specify options.walletVersion=v1):
1278
 * @param options       object      {}
0 ignored issues
show
Documentation introduced by
The parameter options has already been documented on line 1274. The second definition is ignored.
Loading history...
1279
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1275. The second definition is ignored.
Loading history...
1280
 *
1281
 * Or takes four arguments (old, deprecated syntax):
1282
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1283
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1284
 * @param keyIndex      int         override for the blocktrail cosign key to use (for development purposes)
0 ignored issues
show
Documentation introduced by
The parameter keyIndex does not exist. Did you maybe forget to remove this comment?
Loading history...
1285
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1275. The second definition is ignored.
Loading history...
1286
 * @returns {q.Promise}
1287
 */
1288
APIClient.prototype.createNewWallet = function(options, cb) {
1289
    /* jshint -W071, -W074 */
1290
1291
    var self = this;
1292
1293
    if (typeof options !== "object") {
1294
        // get the old-style arguments
1295
        var identifier = arguments[0];
1296
        var passphrase = arguments[1];
1297
        var keyIndex = arguments[2];
1298
        cb = arguments[3];
1299
1300
        // keyIndex is optional
1301
        if (typeof keyIndex === "function") {
1302
            cb = keyIndex;
1303
            keyIndex = null;
1304
        }
1305
1306
        options = {
1307
            identifier: identifier,
1308
            passphrase: passphrase,
1309
            keyIndex: keyIndex
1310
        };
1311
    }
1312
1313
    // default to v3
1314
    options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3;
1315
1316
    var deferred = q.defer();
1317
    deferred.promise.spreadNodeify(cb);
1318
1319
    q.nextTick(function() {
1320
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START);
1321
1322
        options.keyIndex = options.keyIndex || 0;
1323
        options.passphrase = options.passphrase || options.password;
1324
        delete options.password;
1325
1326
        if (!options.identifier) {
1327
            deferred.reject(new blocktrail.WalletCreateError("Identifier is required"));
1328
            return deferred.promise;
1329
        }
1330
1331
        if (options.walletVersion === Wallet.WALLET_VERSION_V1) {
1332
            self._createNewWalletV1(options)
1333
                .progress(function(p) { deferred.notify(p); })
1334
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1335
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1336
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) {
1337
            self._createNewWalletV2(options)
1338
                .progress(function(p) { deferred.notify(p); })
1339
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1340
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1341
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) {
1342
            self._createNewWalletV3(options)
1343
                .progress(function(p) { deferred.notify(p); })
1344
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1345
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1346
        } else {
1347
            deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1348
        }
1349
    });
1350
1351
    return deferred.promise;
1352
};
1353
1354
APIClient.prototype._createNewWalletV1 = function(options) {
1355
    var self = this;
1356
1357
    var deferred = q.defer();
1358
1359
    q.nextTick(function() {
1360
1361
        if (!options.primaryMnemonic && !options.primarySeed) {
1362
            if (!options.passphrase && !options.password) {
1363
                deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase"));
1364
                return deferred.promise;
1365
            } else {
1366
                options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1367
                if (options.storePrimaryMnemonic !== false) {
1368
                    options.storePrimaryMnemonic = true;
1369
                }
1370
            }
1371
        }
1372
1373
        if (!options.backupMnemonic && !options.backupPublicKey) {
1374
            options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1375
        }
1376
1377
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
1378
1379
        self.resolvePrimaryPrivateKeyFromOptions(options)
1380
            .then(function(options) {
1381
                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
1382
1383
                return self.resolveBackupPublicKeyFromOptions(options)
1384
                    .then(function(options) {
1385
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
1386
1387
                        // create a checksum of our private key which we'll later use to verify we used the right password
1388
                        var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1389
                        var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1390
                        var keyIndex = options.keyIndex;
1391
1392
                        var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered();
1393
1394
                        // send the public keys to the server to store them
1395
                        //  and the mnemonic, which is safe because it's useless without the password
1396
                        return self.storeNewWalletV1(
1397
                            options.identifier,
1398
                            [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1399
                            [options.backupPublicKey.toBase58(), "M"],
1400
                            options.storePrimaryMnemonic ? options.primaryMnemonic : false,
1401
                            checksum,
1402
                            keyIndex,
1403
                            options.segwit || null
1404
                        )
1405
                            .then(function(result) {
1406
                                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1407
1408
                                var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1409
                                    return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1410
                                });
1411
1412
                                var wallet = new Wallet(
1413
                                    self,
1414
                                    options.identifier,
1415
                                    Wallet.WALLET_VERSION_V1,
1416
                                    options.primaryMnemonic,
1417
                                    null,
1418
                                    null,
1419
                                    {keyIndex: primaryPublicKey},
1420
                                    options.backupPublicKey,
1421
                                    blocktrailPublicKeys,
1422
                                    keyIndex,
1423
                                    result.segwit || 0,
1424
                                    self.testnet,
1425
                                    self.regtest,
1426
                                    checksum,
1427
                                    result.upgrade_key_index,
1428
                                    options.useCashAddress,
1429
                                    options.bypassNewAddressCheck
1430
                                );
1431
1432
                                return wallet.unlock({
1433
                                    walletVersion: Wallet.WALLET_VERSION_V1,
1434
                                    passphrase: options.passphrase,
1435
                                    primarySeed: options.primarySeed,
1436
                                    primaryMnemonic: null // explicit null
1437
                                }).then(function() {
1438
                                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1439
                                    return [
1440
                                        wallet,
1441
                                        {
1442
                                            walletVersion: wallet.walletVersion,
1443
                                            primaryMnemonic: options.primaryMnemonic,
1444
                                            backupMnemonic: options.backupMnemonic,
1445
                                            blocktrailPublicKeys: blocktrailPublicKeys
1446
                                        }
1447
                                    ];
1448
                                });
1449
                            });
1450
                    }
1451
                );
1452
            })
1453
            .then(
1454
            function(r) {
1455
                deferred.resolve(r);
1456
            },
1457
            function(e) {
1458
                deferred.reject(e);
1459
            }
1460
        )
1461
        ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1462
    });
1463
1464
    return deferred.promise;
1465
};
1466
1467 View Code Duplication
APIClient.prototype._createNewWalletV2 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1468
    var self = this;
1469
1470
    var deferred = q.defer();
1471
1472
    // avoid modifying passed options
1473
    options = _.merge({}, options);
1474
1475
    determineDataStorageV2_3(options)
1476
        .then(function(options) {
1477
            options.passphrase = options.passphrase || options.password;
1478
            delete options.password;
1479
1480
            // avoid deprecated options
1481
            if (options.primaryPrivateKey) {
1482
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1483
            }
1484
1485
            // seed should be provided or generated
1486
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1487
1488
            return options;
1489
        })
1490
        .then(function(options) {
1491
            return produceEncryptedDataV2(options, deferred.notify.bind(deferred));
1492
        })
1493
        .then(function(options) {
1494
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1495
        })
1496
        .then(function(options) {
1497
            // create a checksum of our private key which we'll later use to verify we used the right password
1498
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1499
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1500
            var keyIndex = options.keyIndex;
1501
1502
            // send the public keys and encrypted data to server
1503
            return self.storeNewWalletV2(
1504
                options.identifier,
1505
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1506
                [options.backupPublicKey.toBase58(), "M"],
1507
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1508
                options.storeDataOnServer ? options.encryptedSecret : false,
1509
                options.storeDataOnServer ? options.recoverySecret : false,
1510
                checksum,
1511
                keyIndex,
1512
                options.support_secret || null,
1513
                options.segwit || null
1514
            )
1515
                .then(
1516
                function(result) {
1517
                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1518
1519
                    var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1520
                        return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1521
                    });
1522
1523
                    var wallet = new Wallet(
1524
                        self,
1525
                        options.identifier,
1526
                        Wallet.WALLET_VERSION_V2,
1527
                        null,
1528
                        options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1529
                        options.storeDataOnServer ? options.encryptedSecret : null,
1530
                        {keyIndex: options.primaryPublicKey},
1531
                        options.backupPublicKey,
1532
                        blocktrailPublicKeys,
1533
                        keyIndex,
1534
                        result.segwit || 0,
1535
                        self.testnet,
1536
                        self.regtest,
1537
                        checksum,
1538
                        result.upgrade_key_index,
1539
                        options.useCashAddress,
1540
                        options.bypassNewAddressCheck
1541
                    );
1542
1543
                    // pass along decrypted data to avoid extra work
1544
                    return wallet.unlock({
1545
                        walletVersion: Wallet.WALLET_VERSION_V2,
1546
                        passphrase: options.passphrase,
1547
                        primarySeed: options.primarySeed,
1548
                        secret: options.secret
1549
                    }).then(function() {
1550
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1551
                        return [
1552
                            wallet,
1553
                            {
1554
                                walletVersion: wallet.walletVersion,
1555
                                encryptedPrimarySeed: options.encryptedPrimarySeed ?
1556
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) :
1557
                                    null,
1558
                                backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null,
1559
                                recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1560
                                    bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) :
1561
                                    null,
1562
                                encryptedSecret: options.encryptedSecret ?
1563
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) :
1564
                                    null,
1565
                                blocktrailPublicKeys: blocktrailPublicKeys
1566
                            }
1567
                        ];
1568
                    });
1569
                }
1570
            );
1571
        })
1572
       .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1573
1574
    return deferred.promise;
1575
};
1576
1577 View Code Duplication
APIClient.prototype._createNewWalletV3 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1578
    var self = this;
1579
1580
    var deferred = q.defer();
1581
1582
    // avoid modifying passed options
1583
    options = _.merge({}, options);
1584
1585
    determineDataStorageV2_3(options)
1586
        .then(function(options) {
1587
            options.passphrase = options.passphrase || options.password;
1588
            delete options.password;
1589
1590
            // avoid deprecated options
1591
            if (options.primaryPrivateKey) {
1592
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1593
            }
1594
1595
            // seed should be provided or generated
1596
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1597
1598
            return options;
1599
        })
1600
        .then(function(options) {
1601
            return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred));
1602
        })
1603
        .then(function(options) {
1604
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1605
        })
1606
        .then(function(options) {
1607
            // create a checksum of our private key which we'll later use to verify we used the right password
1608
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1609
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1610
            var keyIndex = options.keyIndex;
1611
1612
            // send the public keys and encrypted data to server
1613
            return self.storeNewWalletV3(
1614
                options.identifier,
1615
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1616
                [options.backupPublicKey.toBase58(), "M"],
1617
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1618
                options.storeDataOnServer ? options.encryptedSecret : false,
1619
                options.storeDataOnServer ? options.recoverySecret : false,
1620
                checksum,
1621
                keyIndex,
1622
                options.support_secret || null,
1623
                options.segwit || null
1624
            )
1625
                .then(
1626
                    // result, deferred, self(apiclient)
1627
                    function(result) {
1628
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1629
1630
                        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1631
                            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1632
                        });
1633
1634
                        var wallet = new Wallet(
1635
                            self,
1636
                            options.identifier,
1637
                            Wallet.WALLET_VERSION_V3,
1638
                            null,
1639
                            options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1640
                            options.storeDataOnServer ? options.encryptedSecret : null,
1641
                            {keyIndex: options.primaryPublicKey},
1642
                            options.backupPublicKey,
1643
                            blocktrailPublicKeys,
1644
                            keyIndex,
1645
                            result.segwit || 0,
1646
                            self.testnet,
1647
                            self.regtest,
1648
                            checksum,
1649
                            result.upgrade_key_index,
1650
                            options.useCashAddress,
1651
                            options.bypassNewAddressCheck
1652
                        );
1653
1654
                        // pass along decrypted data to avoid extra work
1655
                        return wallet.unlock({
1656
                            walletVersion: Wallet.WALLET_VERSION_V3,
1657
                            passphrase: options.passphrase,
1658
                            primarySeed: options.primarySeed,
1659
                            secret: options.secret
1660
                        }).then(function() {
1661
                            deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1662
                            return [
1663
                                wallet,
1664
                                {
1665
                                    walletVersion: wallet.walletVersion,
1666
                                    encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null,
1667
                                    backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null,
1668
                                    recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1669
                                        EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null,
1670
                                    encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null,
1671
                                    blocktrailPublicKeys: blocktrailPublicKeys
1672
                                }
1673
                            ];
1674
                        });
1675
                    }
1676
                );
1677
        })
1678
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1679
1680
    return deferred.promise;
1681
};
1682
1683
function verifyPublicBip32Key(bip32Key, network) {
1684
    var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network);
1685
    if (typeof hk.keyPair.d !== "undefined") {
1686
        throw new Error('BIP32Key contained private key material - abort');
1687
    }
1688
1689
    if (bip32Key[1].slice(0, 1) !== "M") {
1690
        throw new Error("BIP32Key contained non-public path - abort");
1691
    }
1692
}
1693
1694
function verifyPublicOnly(walletData, network) {
1695
    verifyPublicBip32Key(walletData.primary_public_key, network);
1696
    verifyPublicBip32Key(walletData.backup_public_key, network);
1697
}
1698
1699
/**
1700
 * create wallet using the API
1701
 *
1702
 * @param identifier            string      the wallet identifier to create
1703
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1704
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1705
 * @param primaryMnemonic       string      mnemonic to store
1706
 * @param checksum              string      checksum to store
1707
 * @param keyIndex              int         keyIndex that was used to create wallet
1708
 * @param segwit                bool
1709
 * @returns {q.Promise}
1710
 */
1711
APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic,
1712
                                                checksum, keyIndex, segwit) {
1713
    var self = this;
1714
1715
    var postData = {
1716
        identifier: identifier,
1717
        wallet_version: Wallet.WALLET_VERSION_V1,
1718
        primary_public_key: primaryPublicKey,
1719
        backup_public_key: backupPublicKey,
1720
        primary_mnemonic: primaryMnemonic,
1721
        checksum: checksum,
1722
        key_index: keyIndex,
1723
        segwit: segwit
1724
    };
1725
1726
    verifyPublicOnly(postData, self.network);
1727
1728
    return self.blocktrailClient.post("/wallet", null, postData);
1729
};
1730
1731
/**
1732
 * create wallet using the API
1733
 *
1734
 * @param identifier            string      the wallet identifier to create
1735
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1736
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1737
 * @param encryptedPrimarySeed  string      openssl format
1738
 * @param encryptedSecret       string      openssl format
1739
 * @param recoverySecret        string      openssl format
1740
 * @param checksum              string      checksum to store
1741
 * @param keyIndex              int         keyIndex that was used to create wallet
1742
 * @param supportSecret         string
1743
 * @param segwit                bool
1744
 * @returns {q.Promise}
1745
 */
1746
APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1747
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1748
    var self = this;
1749
1750
    var postData = {
1751
        identifier: identifier,
1752
        wallet_version: Wallet.WALLET_VERSION_V2,
1753
        primary_public_key: primaryPublicKey,
1754
        backup_public_key: backupPublicKey,
1755
        encrypted_primary_seed: encryptedPrimarySeed,
1756
        encrypted_secret: encryptedSecret,
1757
        recovery_secret: recoverySecret,
1758
        checksum: checksum,
1759
        key_index: keyIndex,
1760
        support_secret: supportSecret || null,
1761
        segwit: segwit
1762
    };
1763
1764
    verifyPublicOnly(postData, self.network);
1765
1766
    return self.blocktrailClient.post("/wallet", null, postData);
1767
};
1768
1769
/**
1770
 * create wallet using the API
1771
 *
1772
 * @param identifier            string      the wallet identifier to create
1773
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1774
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1775
 * @param encryptedPrimarySeed  Buffer      buffer of ciphertext
1776
 * @param encryptedSecret       Buffer      buffer of ciphertext
1777
 * @param recoverySecret        Buffer      buffer of recovery secret
1778
 * @param checksum              string      checksum to store
1779
 * @param keyIndex              int         keyIndex that was used to create wallet
1780
 * @param supportSecret         string
1781
 * @param segwit                bool
1782
 * @returns {q.Promise}
1783
 */
1784
APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1785
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1786
    var self = this;
1787
1788
    var postData = {
1789
        identifier: identifier,
1790
        wallet_version: Wallet.WALLET_VERSION_V3,
1791
        primary_public_key: primaryPublicKey,
1792
        backup_public_key: backupPublicKey,
1793
        encrypted_primary_seed: encryptedPrimarySeed.toString('base64'),
1794
        encrypted_secret: encryptedSecret.toString('base64'),
1795
        recovery_secret: recoverySecret.toString('hex'),
1796
        checksum: checksum,
1797
        key_index: keyIndex,
1798
        support_secret: supportSecret || null,
1799
        segwit: segwit
1800
    };
1801
1802
    verifyPublicOnly(postData, self.network);
1803
1804
    return self.blocktrailClient.post("/wallet", null, postData);
1805
};
1806
1807
/**
1808
 * create wallet using the API
1809
 *
1810
 * @param identifier            string      the wallet identifier to create
1811
 * @param postData              object
1812
 * @param [cb]                  function    callback(err, result)
1813
 * @returns {q.Promise}
1814
 */
1815
APIClient.prototype.updateWallet = function(identifier, postData, cb) {
1816
    var self = this;
1817
1818
    return self.blocktrailClient.post("/wallet/" + identifier, null, postData, cb);
1819
};
1820
1821
/**
1822
 * upgrade wallet to use a new account number
1823
 *  the account number specifies which blocktrail cosigning key is used
1824
 *
1825
 * @param identifier            string      the wallet identifier
1826
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1827
 * @param keyIndex              int         keyIndex that was used to create wallet
1828
 * @param [cb]                  function    callback(err, result)
1829
 * @returns {q.Promise}
1830
 */
1831
APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) {
1832
    var self = this;
1833
1834
    return self.blocktrailClient.post("/wallet/" + identifier + "/upgrade", null, {
1835
        key_index: keyIndex,
1836
        primary_public_key: primaryPublicKey
1837
    }, cb);
1838
};
1839
1840
/**
1841
 * get the balance for the wallet
1842
 *
1843
 * @param identifier            string      the wallet identifier
1844
 * @param [cb]                  function    callback(err, result)
1845
 * @returns {q.Promise}
1846
 */
1847
APIClient.prototype.getWalletBalance = function(identifier, cb) {
1848
    var self = this;
1849
1850
    return self.blocktrailClient.get("/wallet/" + identifier + "/balance", null, true, cb);
1851
};
1852
1853
/**
1854
 * do HD wallet discovery for the wallet
1855
 *
1856
 * @param identifier            string      the wallet identifier
1857
 * @param [cb]                  function    callback(err, result)
1858
 * @returns {q.Promise}
1859
 */
1860
APIClient.prototype.doWalletDiscovery = function(identifier, gap, cb) {
1861
    var self = this;
1862
1863
    return self.blocktrailClient.get("/wallet/" + identifier + "/discovery", {gap: gap}, true, cb);
1864
};
1865
1866
1867
/**
1868
 * get a new derivation number for specified parent path
1869
 *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
1870
 *
1871
 * @param identifier            string      the wallet identifier
1872
 * @param path                  string      the parent path for which to get a new derivation,
1873
 *                                           can be suffixed with /* to make it clear on which level the derivations hould be
1874
 * @param [cb]                  function    callback(err, result)
1875
 * @returns {q.Promise}
1876
 */
1877
APIClient.prototype.getNewDerivation = function(identifier, path, cb) {
1878
    var self = this;
1879
1880
    return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb);
1881
};
1882
1883
1884
/**
1885
 * delete the wallet
1886
 *  the checksum address and a signature to verify you ownership of the key of that checksum address
1887
 *  is required to be able to delete a wallet
1888
 *
1889
 * @param identifier            string      the wallet identifier
1890
 * @param checksumAddress       string      the address for your master private key (and the checksum used when creating the wallet)
1891
 * @param checksumSignature     string      a signature of the checksum address as message signed by the private key matching that address
1892
 * @param [force]               bool        ignore warnings (such as a non-zero balance)
1893
 * @param [cb]                  function    callback(err, result)
1894
 * @returns {q.Promise}
1895
 */
1896
APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) {
1897
    var self = this;
1898
1899
    if (typeof force === "function") {
1900
        cb = force;
1901
        force = false;
1902
    }
1903
1904
    return self.blocktrailClient.delete("/wallet/" + identifier, {force: force}, {
1905
        checksum: checksumAddress,
1906
        signature: checksumSignature
1907
    }, cb);
1908
};
1909
1910
/**
1911
 * use the API to get the best inputs to use based on the outputs
1912
 *
1913
 * the return array has the following format:
1914
 * [
1915
 *  "utxos" => [
1916
 *      [
1917
 *          "hash" => "<txHash>",
1918
 *          "idx" => "<index of the output of that <txHash>",
1919
 *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
1920
 *          "value" => 32746327,
1921
 *          "address" => "1address",
1922
 *          "path" => "m/44'/1'/0'/0/13",
1923
 *          "redeem_script" => "<redeemScript-hex>",
1924
 *      ],
1925
 *  ],
1926
 *  "fee"   => 10000,
1927
 *  "change"=> 1010109201,
1928
 * ]
1929
 *
1930
 * @param identifier        string      the wallet identifier
1931
 * @param pay               array       {'address': (int)value}     coins to send
1932
 * @param lockUTXO          bool        lock UTXOs for a few seconds to allow for transaction to be created
1933
 * @param allowZeroConf     bool        allow zero confirmation unspent outputs to be used in coin selection
1934
 * @param feeStrategy       string      defaults to
1935
 * @param options
1936
 * @param [cb]              function    callback(err, utxos, fee, change)
1937
 * @returns {q.Promise}
1938
 */
1939
APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1940
    var self = this;
1941
1942
    if (typeof feeStrategy === "function") {
1943
        cb = feeStrategy;
1944
        feeStrategy = null;
1945
        options = {};
1946
    } else if (typeof options === "function") {
1947
        cb = options;
1948
        options = {};
1949
    }
1950
1951
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1952
    options = options || {};
1953
1954
    var deferred = q.defer();
1955
    deferred.promise.spreadNodeify(cb);
1956
1957
    var params = {
1958
        lock: lockUTXO,
1959
        zeroconf: allowZeroConf ? 1 : 0,
1960
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
1961
        fee_strategy: feeStrategy
1962
    };
1963
1964
    if (options.forcefee) {
1965
        params['forcefee'] = options.forcefee;
1966
    }
1967
1968
    deferred.resolve(
1969
        self.blocktrailClient.post("/wallet/" + identifier + "/coin-selection", params, pay).then(
1970
            function(result) {
1971
                return [result.utxos, result.fee, result.change, result];
1972
            },
1973
            function(err) {
1974
                if (err.message.match(/too low to pay the fee/)) {
1975
                    throw blocktrail.WalletFeeError(err);
1976
                }
1977
1978
                throw err;
1979
            }
1980
        )
1981
    );
1982
1983
    return deferred.promise;
1984
};
1985
1986
/**
1987
 * @param [cb]              function    callback(err, utxos, fee, change)
1988
 * @returns {q.Promise}
1989
 */
1990
APIClient.prototype.feePerKB = function(cb) {
1991
    var self = this;
1992
1993
    var deferred = q.defer();
1994
    deferred.promise.spreadNodeify(cb);
1995
1996
    deferred.resolve(self.blocktrailClient.get("/fee-per-kb"));
1997
1998
    return deferred.promise;
1999
};
2000
2001
/**
2002
 * send the transaction using the API
2003
 *
2004
 * @param identifier        string      the wallet identifier
2005
 * @param txHex             string      partially signed transaction as hex string
2006
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
2007
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
2008
 * @param [twoFactorToken]  string      2FA token
2009
 * @param [prioboost]       bool
2010
 * @param [cb]              function    callback(err, txHash)
2011
 * @returns {q.Promise}
2012
 */
2013
APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
2014
    var self = this;
2015
2016
    if (typeof twoFactorToken === "function") {
2017
        cb = twoFactorToken;
2018
        twoFactorToken = null;
2019
        prioboost = false;
2020
    } else if (typeof prioboost === "function") {
2021
        cb = prioboost;
2022
        prioboost = false;
2023
    }
2024
2025
    var data = {
2026
        paths: paths,
2027
        two_factor_token: twoFactorToken
2028
    };
2029
    if (typeof txHex === "string") {
2030
        data.raw_transaction = txHex;
2031
    } else if (typeof txHex === "object") {
2032
        Object.keys(txHex).map(function(key) {
2033
            data[key] = txHex[key];
2034
        });
2035
    }
2036
2037
    return self.blocktrailClient.post(
2038
        "/wallet/" + identifier + "/send",
2039
        {
2040
            check_fee: checkFee ? 1 : 0,
2041
            prioboost: prioboost ? 1 : 0
2042
        },
2043
        data,
2044
        cb
2045
    );
2046
};
2047
2048
/**
2049
 * setup a webhook for this wallet
2050
 *
2051
 * @param identifier        string      the wallet identifier
2052
 * @param webhookIdentifier string      identifier for the webhook
2053
 * @param url               string      URL to receive webhook events
2054
 * @param [cb]              function    callback(err, webhook)
2055
 * @returns {q.Promise}
2056
 */
2057
APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) {
2058
    var self = this;
2059
2060
    return self.blocktrailClient.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb);
2061
};
2062
2063
/**
2064
 * delete a webhook that was created for this wallet
2065
 *
2066
 * @param identifier        string      the wallet identifier
2067
 * @param webhookIdentifier string      identifier for the webhook
2068
 * @param [cb]              function    callback(err, success)
2069
 * @returns {q.Promise}
2070
 */
2071
APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) {
2072
    var self = this;
2073
2074
    return self.blocktrailClient.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb);
2075
};
2076
2077
/**
2078
 * get all transactions for an wallet (paginated)
2079
 *
2080
 * @param identifier    string      wallet identifier
2081
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2082
 * @param [cb]          function    callback function to call when request is complete
2083
 * @return q.Promise
2084
 */
2085
APIClient.prototype.walletTransactions = function(identifier, params, cb) {
2086
    var self = this;
2087
2088
    if (typeof params === "function") {
2089
        cb = params;
2090
        params = null;
2091
    }
2092
2093
    return self.blocktrailClient.get("/wallet/" + identifier + "/transactions", params, true, cb);
2094
};
2095
2096
/**
2097
 * get all addresses for an wallet (paginated)
2098
 *
2099
 * @param identifier    string      wallet identifier
2100
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2101
 * @param [cb]          function    callback function to call when request is complete
2102
 * @return q.Promise
2103
 */
2104
APIClient.prototype.walletAddresses = function(identifier, params, cb) {
2105
    var self = this;
2106
2107
    if (typeof params === "function") {
2108
        cb = params;
2109
        params = null;
2110
    }
2111
2112
    return self.blocktrailClient.get("/wallet/" + identifier + "/addresses", params, true, cb);
2113
};
2114
2115
/**
2116
 * @param identifier    string      wallet identifier
2117
 * @param address       string      the address to label
2118
 * @param label         string      the label
2119
 * @param [cb]          function    callback(err, res)
2120
 * @return q.Promise
2121
 */
2122
APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) {
2123
    var self = this;
2124
2125
    return self.blocktrailClient.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb);
2126
};
2127
2128
APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) {
2129
    var self = this;
2130
2131
    if (typeof feeStrategy === "function") {
2132
        cb = feeStrategy;
2133
        feeStrategy = null;
2134
    } else if (typeof options === "function") {
2135
        cb = options;
2136
        options = {};
2137
    }
2138
2139
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
2140
    options = options || {};
2141
2142
    var params = {
2143
        outputs: options.outputs ? options.outputs : 1,
2144
        zeroconf: allowZeroConf ? 1 : 0,
2145
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
2146
        fee_strategy: feeStrategy
2147
    };
2148
2149
    if (options.forcefee) {
2150
        params['forcefee'] = options.forcefee;
2151
    }
2152
2153
    return self.blocktrailClient.get("/wallet/" + identifier + "/max-spendable", params, true, cb);
2154
};
2155
2156
/**
2157
 * get all UTXOs for an wallet (paginated)
2158
 *
2159
 * @param identifier    string      wallet identifier
2160
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2161
 * @param [cb]          function    callback function to call when request is complete
2162
 * @return q.Promise
2163
 */
2164
APIClient.prototype.walletUTXOs = function(identifier, params, cb) {
2165
    var self = this;
2166
2167
    if (typeof params === "function") {
2168
        cb = params;
2169
        params = null;
2170
    }
2171
2172
    return self.blocktrailClient.get("/wallet/" + identifier + "/utxos", params, true, cb);
2173
};
2174
2175
/**
2176
 * get a paginated list of all wallets associated with the api user
2177
 *
2178
 * @param [params]      object      pagination: {page: 1, limit: 20}
2179
 * @param [cb]          function    callback function to call when request is complete
2180
 * @return q.Promise
2181
 */
2182
APIClient.prototype.allWallets = function(params, cb) {
2183
    var self = this;
2184
2185
    if (typeof params === "function") {
2186
        cb = params;
2187
        params = null;
2188
    }
2189
2190
    return self.blocktrailClient.get("/wallets", params, true, cb);
2191
};
2192
2193
/**
2194
 * verify a message signed bitcoin-core style
2195
 *
2196
 * @param message        string
2197
 * @param address        string
2198
 * @param signature      string
2199
 * @param [cb]          function    callback function to call when request is complete
2200
 * @return q.Promise
2201
 */
2202
APIClient.prototype.verifyMessage = function(message, address, signature, cb) {
2203
    var self = this;
2204
2205
    var deferred = q.defer();
2206
    deferred.promise.nodeify(cb);
2207
    try {
2208
        var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64'));
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2209
        deferred.resolve(result);
2210
    } catch (e) {
2211
        deferred.reject(e);
2212
    }
2213
2214
    return deferred.promise;
2215
};
2216
2217
/**
2218
 * max is 0.001
2219
 * testnet only
2220
 *
2221
 * @param address
2222
 * @param amount
2223
 * @param cb
2224
 */
2225
APIClient.prototype.faucetWithdrawl = function(address, amount, cb) {
2226
    var self = this;
2227
2228
    return self.blocktrailClient.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb);
2229
};
2230
2231
/**
2232
 * send a raw transaction
2233
 *
2234
 * @param rawTransaction    string      raw transaction as HEX
2235
 * @param [cb]              function    callback function to call when request is complete
2236
 * @return q.Promise
2237
 */
2238
APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) {
2239
    var self = this;
2240
2241
    return self.blocktrailClient.post("/send-raw-tx", null, rawTransaction, cb);
2242
};
2243
2244
/**
2245
 * get the current price index
2246
 *
2247
 * @param [cb]          function    callback({'USD': 287.30})
2248
 * @return q.Promise
2249
 */
2250
APIClient.prototype.price = function(cb) {
2251
    var self = this;
2252
2253
    return self.blocktrailClient.get("/price", null, false, cb);
2254
};
2255
2256
module.exports = APIClient;
2257